package edu.whu.rose.clock.kps;

import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.ListIterator;

import com.sleepycat.je.DatabaseException;

import edu.whu.rose.clock.kps.algorithm.SearchAlgorithm;
import edu.whu.rose.clock.kps.algorithm.cbes.CESwithPrioritization;
import edu.whu.rose.clock.kps.algorithm.cbes.ConcurrentBackwardExpandingSearch;
import edu.whu.rose.clock.kps.algorithm.cbes.ConcurrentBackwardExpandingSearchWithEagerness;
import edu.whu.rose.clock.kps.algorithm.cbes.MyBestAlgorithm;
import edu.whu.rose.clock.kps.algorithm.sortedlists.SortedListSearch;
import edu.whu.rose.clock.kps.datastruct.PrimitiveMatchedSubgraph;
import edu.whu.rose.clock.kps.datastruct.SimpleAnswer;
import edu.whu.rose.clock.kps.datastruct.SimpleMatchedSubgraph;
import edu.whu.rose.clock.kps.datastruct.SimpleSubgraphRecord;
import edu.whu.rose.clock.kps.datastruct.SimpleSubgraphRecordGroup;
import edu.whu.rose.clock.kps.graph.GraphMatrix;
import edu.whu.rose.clock.kps.spindex.ShortestPathIndexDB;
import edu.whu.rose.clock.setup.IndexEnvironment;
import edu.whu.rose.clock.ssql.graph.RRadiusSubGraph;
import edu.whu.rose.clock.ssql.graphindex.GraphIndexDB;
import edu.whu.rose.clock.ssql.graphindex.GraphIndexItem;
import edu.whu.rose.clock.ssql.index.VertexIndexDB;
import edu.whu.rose.clock.ssql.subgraph.SubgraphDB;
import edu.whu.rose.clock.ssql.util.SubgraphMatrix;

public class SimpleQueryProcessingManager {

	private GraphIndexDB gidb;
	private VertexIndexDB vidb;
	private SubgraphDB sgdb;
	
	private GraphMatrix graph;
	
	private FileWriter fw;
	
	public SimpleQueryProcessingManager(GraphIndexDB gidb, VertexIndexDB vidb, SubgraphDB sgdb) {
		this.gidb = gidb;
		this.vidb = vidb;
		this.sgdb = sgdb;
	}
	
	public SimpleQueryProcessingManager(GraphIndexDB gidb, VertexIndexDB vidb, SubgraphDB sgdb, 
			GraphMatrix graph, FileWriter fw) {
		this.gidb = gidb;
		this.vidb = vidb;
		this.sgdb = sgdb;
		this.graph = graph;
		this.fw = fw;
	}
	
	public static void main(String[] args) {
		IndexEnvironment ie1 = new IndexEnvironment("dbEnv_graphindex");
		ie1.openEnv();
		GraphIndexDB gidb = new GraphIndexDB(ie1.getEnvironment());
		if (!gidb.openDB()) {
			System.exit(1);
		}
		
		IndexEnvironment ie2 = new IndexEnvironment("dbEnv_vertexindex");
		ie2.openEnv();
		VertexIndexDB vidb = new VertexIndexDB(ie2.getEnvironment());
		if (!vidb.openDB()) {
			System.exit(1);
		}

		SubgraphDB sgdb = new SubgraphDB("dbEnv_subgraph");
		if (!(sgdb.initEnv() & sgdb.openDB())) {
			System.exit(1);
		}
		
		GraphMatrix graph = new GraphMatrix(198467);
		graph.loadEdges("graph/edge.txt");
		
		FileWriter fw = null;
		try {
			fw = new FileWriter("mine [].txt");
		}
		catch (IOException ex) {
			ex.printStackTrace();
		}
		SimpleQueryProcessingManager sqpm = new SimpleQueryProcessingManager(gidb, vidb, sgdb, graph, fw);
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		SimpleAnswer[] answers = null;
		int k = 50;
		try {
			while (true) {
				System.out.println("Please input query:");
				String statement = br.readLine().trim();
				if (!statement.equals("exit")) {
					Date startTime = new Date();
//					for (int y = 0; y < 10; y++) {
						answers = sqpm.search(SearchAlgorithm.BEST_ALGORITHM, statement, k);
//					}
					Date endTime = new Date();
					if (answers != null) {
						double sum = 0.0d;
						int i = 0;
						for (; i < answers.length; i++) {
							SimpleAnswer at = answers[i];
							if (at == null) {
								break;
							}
							System.out.print("Rank " + (i + 1) + ": {" + at.getScore() + "} ");
							System.out.print(" root[" + at.getRoot() + "] ");
							sum += at.getScore();
							for (int j = 0; j < at.getLeafNumber(); j++) {
								int[] path = at.getPath(j);
								System.out.print("( ");
								if (path != null) {
									for (int m = 0; m < path.length; m++) {
										System.out.print(path[m] + ", ");
									}
								}
								System.out.print(" )");
							}
							System.out.println();
						}
						if (i == 0) {
							System.out.println("Sorry, no result is found.");
						}
						else {
							System.out.println("Average score: " + (sum / Math.min(k, i)));
						}
						
//						System.out.println("Total time exhausted: " + (endTime.getTime() - startTime.getTime()) + " ms");
					}
					else {
						System.out.println("Sorry, no result is found.");
					}
				}
				else {
					break;
				}
			}
			

			fw.close();
		}
		catch (IOException ex) {
			ex.printStackTrace();
		}
		
		sgdb.closeDB();
		vidb.closeDB();
		gidb.closeDB();
		ie2.closeEnv();
		ie1.closeEnv();
	}
	
	public SimpleAnswer[] search(int type, String statement, int k) throws IOException {
		String[] keywords = statement.split(" ");
		
		int[] ks = new int[]{10, 25, 50};
		int z = 1;

		long[] totalInitTimes = new long[ks.length];
		long[] totalFirstkTimes = new long[ks.length];
		long[] totalTopkTimes = new long[ks.length];
		
		try {
			// statistic in full graph
			fw.write("Number of matched vertexes in full graph: ( ");
			for (int i = 0; i < keywords.length; i++) {
				String keyword = keywords[i];
				int[] matchedVertexes = vidb.search(keyword);
				fw.write(matchedVertexes.length + " ");
			}
			fw.write(")\r\n");
			
			// search
			SimpleSubgraphRecordGroup[] groups = produceGroups(keywords);
			
			// union
			SimpleMatchedSubgraph[] matched_subgraphs = null;
			long unionTime = 0;
			for (int w = 0; w < z; w++) {
				Date unionstart = new Date();
				matched_subgraphs = intersectGroups(groups);
				Date unionend = new Date();
				unionTime += unionend.getTime() - unionstart.getTime();
			}
			unionTime = unionTime / z;
			fw.write("Time exhausted for unioning groups: " + unionTime + "ms\r\n");
			if (matched_subgraphs == null) {
				return null;
			}
			
			SearchAlgorithm sa = null;
			if (type == SearchAlgorithm.CONCURRENT_BACKWARD_EXPANDING) {
				loadSubgraphs(matched_subgraphs);
				sa = new ConcurrentBackwardExpandingSearch(matched_subgraphs);
				sa.init();
				return sa.process(k);
			}
			else if (type == SearchAlgorithm.CONCURRENT_BACKWARD_EXPANDING_WITH_EAGERNESS) {
				loadSubgraphs(matched_subgraphs);
				sa = new ConcurrentBackwardExpandingSearchWithEagerness(matched_subgraphs);
				sa.init();
				return sa.process(k);
			}
			else if (type == SearchAlgorithm.SORTED_LIST_SEARCH) {
				IndexEnvironment ie = new IndexEnvironment("dbEnv_shortestpath");
				if (!ie.openEnv()) {
					System.exit(1);
				}
				ShortestPathIndexDB disDB = new ShortestPathIndexDB(ie.getEnvironment(), "distances");
				disDB.openDB();
				ShortestPathIndexDB preDB = new ShortestPathIndexDB(ie.getEnvironment(), "predecessors");
				preDB.openDB();
				
				sa = new SortedListSearch(groups.length, matched_subgraphs, disDB, preDB, sgdb);
				sa.init();
				SimpleAnswer[] result = sa.process(k);
				
				disDB.closeDB();
				preDB.closeDB();
				
				return result;
			}
			else if (type == SearchAlgorithm.CONCURRENT_DISTINCT_ROOT) {
				// statistic in matched subgraphs
				System.out.println("Number of matched subgraphs: " + matched_subgraphs.length);
				int[] vertexNum = new int[keywords.length];
				for (int i = 0; i < matched_subgraphs.length; i++) {
					for (int j = 0; j < vertexNum.length; j++) {
						vertexNum[j] += matched_subgraphs[i].getGroup(j).length;
					}
				}
				System.out.print("Number of matched vertexes: ( ");
				for (int j = 0; j < vertexNum.length; j++) {
					System.out.print(vertexNum[j] + " ");
				}
				System.out.println(")");

				if (matched_subgraphs.length == 0) {
					return null;
				}
				loadSubgraphs(matched_subgraphs);
				Date start = new Date();
				sa = new CESwithPrioritization(matched_subgraphs, start);
				sa.init();
				SimpleAnswer[] result = sa.process(k);
				Date end = new Date();
				System.out.println("Time exhausted for processing: " + (end.getTime() - start.getTime()) + "ms");
				return result;
			}
			else if (type == SearchAlgorithm.BEST_ALGORITHM) {
				// statistic in matched subgraphs
				fw.write("Number of matched subgraphs: " + matched_subgraphs.length + "\r\n");
				int[] vertexNum = new int[keywords.length];
				for (int i = 0; i < matched_subgraphs.length; i++) {
					for (int j = 0; j < vertexNum.length; j++) {
						vertexNum[j] += matched_subgraphs[i].getGroup(j).length;
					}
				}
				fw.write("Number of matched vertexes in all subgraphs: ( ");
				for (int j = 0; j < vertexNum.length; j++) {
					fw.write(vertexNum[j] + " ");
				}
				fw.write(")\r\n");

				if (matched_subgraphs.length == 0) {
					return null;
				}
				
				Date start = new Date();
				PrimitiveMatchedSubgraph[] pmss = loadRRadiusSubGraphs(matched_subgraphs);
				Date end = new Date();
				fw.write("Time exhausted for loading subgraphs: " + 
						(end.getTime() - start.getTime()) + "ms\r\n");
				
				
				sa = new MyBestAlgorithm(graph, pmss, fw);
				
				boolean printGourpSize = true;
				
				SimpleAnswer[] result = null;
				for (int v = 0; v < ks.length; v++) {
					((MyBestAlgorithm) sa).needPrint = true;
					for (int w = 0; w < z; w++) {
						sa.init();
						if (printGourpSize) {
							fw.write("Number of matched vertexes in all subgraphs: ( ");
							for (int i = 0; i < matched_subgraphs[0].groupNum(); i++) {
								fw.write(((MyBestAlgorithm) sa).initVertexNums[i] + " ");
							}
							fw.write(")\r\n");
							printGourpSize = false;
						}
						result = sa.process(ks[v]);

						fw.write("Time exhausted for initialization: " + 
								((MyBestAlgorithm) sa).initTime + " ms\r\n");
						fw.write("Time exhausted for searching first-k answers: " + 
								((MyBestAlgorithm) sa).firstkTime + "ms\r\n");
						fw.write("Time exhausted for searching top-k answers: " + 
								((MyBestAlgorithm) sa).topkTime + " ms\r\n");
						
						totalInitTimes[v] += ((MyBestAlgorithm) sa).initTime;
						totalFirstkTimes[v] += ((MyBestAlgorithm) sa).firstkTime;
						totalTopkTimes[v] += ((MyBestAlgorithm) sa).topkTime;

						System.out.println("repeated " + (w + 1) + " times");
						((MyBestAlgorithm) sa).needPrint = false;
					}
					System.out.println("top-" + ks[v] + " finished");
					fw.write("\r\n");
					fw.write("\r\n");
					fw.write("\r\n");
					fw.write("\r\n");
					fw.write("\r\n");
				}
				
				fw.write("\r\n");
				fw.write("\r\n");
				fw.write("\r\n");
				fw.write("\r\n");
				fw.write("\r\n");
				for (int v = 0; v < ks.length; v++) {
					fw.write("Average time exhausted for initialization: " + 
							((double)totalInitTimes[v] / z + unionTime) + "ms\r\n");
					fw.write("Average time exhausted for searching first-" + ks[v] +" answers: " + 
							((double)totalFirstkTimes[v] / z) + "ms\r\n");
//					fw.write("Real average time exhausted for searching first-" + ks[v] +" answers: " + 
//							((double)totalFirstkTimes[v] / z + (double)totalInitTimes[v] / z + unionTime) + "ms\r\n");
					fw.write("Average time exhausted for searching top-" + ks[v] +" answers: " + 
							((double)totalTopkTimes[v] / z) + "ms\r\n");
//					fw.write("Real average time exhausted for searching top-" + ks[v] +" answers: " + 
//							((double)totalTopkTimes[v] / z + (double)totalFirstkTimes[v] / z + (double)totalInitTimes[v] / z + unionTime) + "ms\r\n");
					fw.write("\r\n");
				}
				
				return result;
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
		}
		return null;
	}

	public SimpleAnswer[] search(String[] keywords) {
		try {
			SimpleSubgraphRecordGroup[] groups = produceGroups(keywords);
			SimpleMatchedSubgraph[] matched_subgraphs = intersectGroups(groups);
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
		}
		return null;
	}
	
	private SimpleSubgraphRecordGroup[] produceGroups(String[] keywords) throws DatabaseException {
		int size = keywords.length;
		SimpleSubgraphRecordGroup[] groups = new SimpleSubgraphRecordGroup[size];
		for (int i = 0; i < size; i++) {
			groups[i] = lookupGraphIndex(i + 1, keywords[i]);
		}
		return groups;
	}
	
	private SimpleSubgraphRecordGroup lookupGraphIndex(int id, String keyword) throws DatabaseException {
		Date start = new Date();
		SimpleSubgraphRecordGroup ssrg = new SimpleSubgraphRecordGroup(id, keyword);
		GraphIndexItem gii = gidb.search(keyword);
		if (gii == null) {
			return null;
		}
		int[] subgraphs = gii.getGraphs();
		
		for (int i = 0; i < subgraphs.length; i++) {
			int[] vertexes = gii.getVertexGroup(i);
			SimpleSubgraphRecord ssr = new SimpleSubgraphRecord(subgraphs[i], vertexes);
			ssrg.addRecord(ssr);
		}
		
//		int[] allVertexes = vidb.search(keyword);
//		for (int i = 0; i < subgraphs.length; i++) {
//			int[] vertexes = sgdb.getSubGraph(subgraphs[i]).getVertexes();
//			vertexes = merge(vertexes, allVertexes);
//			SimpleSubgraphRecord ssr = new SimpleSubgraphRecord(subgraphs[i], vertexes);
//			ssrg.addRecord(ssr);
//		}
		
		Date end = new Date();
		try {
			fw.write("Produced group " + id + ": " + + (end.getTime() - start.getTime()) + "ms");
		}
		catch (IOException ex) {
			ex.printStackTrace();
		}
		return ssrg;
	}
	
	private SimpleMatchedSubgraph[] intersectGroups(SimpleSubgraphRecordGroup[] groups) {
		ArrayList<SimpleMatchedSubgraph> matched = new ArrayList<SimpleMatchedSubgraph>();
		
		if (groups[0] != null) {
			for (int i = 0; i < groups[0].getSize(); i++) {
				boolean match = true;
				int[][] temp = new int[groups.length][];
				temp[0] = groups[0].getVertexes(i);
				for (int j = 1; j < groups.length; j++) {
					if (groups[j] == null) {
						return null;
					}
					if (match) {
						match = false;
						
						int low = 0;
						int mid = 0;
						int top = groups[j].getSize() - 1;
						
						while (low <= top) {
							mid = (low + top) / 2;
							if (groups[0].getSubgraph(i) < groups[j].getSubgraph(mid)) {
								top = mid - 1;
							}
							else if (groups[0].getSubgraph(i) > groups[j].getSubgraph(mid)) {
								low = mid + 1;
							}
							else if (groups[0].getSubgraph(i) == groups[j].getSubgraph(mid)) {
								match = true;
								temp[j] = groups[j].getVertexes(mid);
								break;
							}
						}
					}
					else {
						break;
					}
				}
				if (match) {
					matched.add(new SimpleMatchedSubgraph(groups[0].getSubgraph(i), temp));
				}
			}
			
			SimpleMatchedSubgraph[] result = new SimpleMatchedSubgraph[matched.size()];
			ListIterator<SimpleMatchedSubgraph> it = matched.listIterator();
			int i = 0;
			while (it.hasNext()) {
				result[i] = it.next();
				i++;
			}
			return result;
		}
		return null;
	}
	
	private void loadSubgraphs(SimpleMatchedSubgraph[] matched_subgraphs) {
		for (int i = 0; i < matched_subgraphs.length; i++) {
			try {
				RRadiusSubGraph rrsg = sgdb.getSubGraph(matched_subgraphs[i].getSubgraphID());
				SubgraphMatrix sm = new SubgraphMatrix(rrsg);
				matched_subgraphs[i].setSubgraphMatrix(sm);
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
			}
		}
	}
	
	private PrimitiveMatchedSubgraph[] loadRRadiusSubGraphs(SimpleMatchedSubgraph[] matched_subgraphs) {
		PrimitiveMatchedSubgraph[] result = new PrimitiveMatchedSubgraph[matched_subgraphs.length];
		for (int i = 0; i < matched_subgraphs.length; i++) {
			try {
				RRadiusSubGraph rrsg = sgdb.getSubGraph(matched_subgraphs[i].getSubgraphID());
				PrimitiveMatchedSubgraph pms = 
					new PrimitiveMatchedSubgraph(matched_subgraphs[i].getSubgraphID(), 
							matched_subgraphs[i].getGroups());
				pms.setSubgraph(rrsg);
				result[i] = pms;
			}
			catch (DatabaseException ex) {
				ex.printStackTrace();
			}
		}
		return result;
	}
	
	private void statisticInFullGraph() {
		
	}
	
//	private int[] merge(int[] arr1, int[] arr2) {
//		ExpandableIntArray eia = new ExpandableIntArray();
//		int low = 0;
//		for (int i = 0; i < arr1.length; i++) {
//			int mid = 0;
//			int top = arr2.length - 1;
//			while (low <= top) {
//				mid = (low + top) / 2;
//				if (arr1[i] > arr2[mid]) {
//					low = mid + 1;
//				} else if (arr1[i] < arr2[mid]) {
//					top = mid - 1;
//				} else {
//					eia.add(arr1[i]);
//					break;
//				}
//			}
//		}
//		return eia.toArray();
//	}

}